﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using CoreEx.Common.Linq;
using LinFu.IoC.Configuration;

namespace CoreEx.Database.Linq.Implementation
{
    /// <summary>
    /// Rewrites an <see cref="Expression"/> tree by replacing references 
    /// to <see cref="bool"/> members with a <see cref="BinaryExpression"/>.
    /// This is done because we want a consise way of handling boolean binary expressions.
    /// <remarks>
    /// The general rule here is that if the <see cref="MemberExpression"/> is not already part of 
    /// a <see cref="BinaryExpression"/> we create a new <see cref="BinaryExpression"/>
    /// <code>
    /// (p => p.Discontinued) 
    /// </code>    
    /// translates into 
    /// <code>
    /// p => p.Discontinued == true</code>
    /// </remarks>
    /// </summary>
    [Implements(typeof(IExpressionRewriter),ServiceName = "BooleanRewriter")]
    public class BooleanRewriter : ExpressionVisitor , IExpressionRewriter
    {
        private readonly HashSet<Expression> _expressions = new HashSet<Expression>();

        public Expression Rewrite(Expression expression)
        {
            return Visit(expression);
        }
                
        protected static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }


        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Type == typeof(bool) && !ParticipatesInBinaryExpression(m))
            {                
                return Expression.MakeBinary(ExpressionType.Equal, m, Expression.Constant(true));
            }
            return base.VisitMemberAccess(m);
        }


        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b.Type == typeof(bool) && (b.NodeType == ExpressionType.Equal || b.NodeType == ExpressionType.NotEqual))
            {
                RegisterAsBinaryParticipant(b.Left);
                RegisterAsBinaryParticipant(b.Right);
            }
            return base.VisitBinary(b);
        }

        protected virtual bool ParticipatesInBinaryExpression(MemberExpression memberExpression)
        {
            return _expressions.Contains(memberExpression);
        }


        private void RegisterAsBinaryParticipant(Expression expression)
        {
            if (expression is UnaryExpression)
                expression = ((UnaryExpression) expression).Operand;
            if (!_expressions.Contains(expression))
                _expressions.Add(expression);
        }



        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.Name == "Where")
            {
                var lambdaExpression = (LambdaExpression)StripQuotes(m.Arguments[1]);
                Visit(lambdaExpression);
            }
            return base.VisitMethodCall(m);
        }


    }
}
